"""
Most Nigerian phone numbers consist of:
* A plus (+) sign, followed by the country prefix -- this is optional.
* A mobile prefix (for mobile lines) or area code (for "land" lines).
* The rest of the number -- mobile numbers will be 7 digits long; land lines may be 6 or 7 digits long.

This module exports:
* A list of local exchange codes and mobile network prefixes for Nigerian phone numbers.
* Functions to help validate and normalize Nigerian phone numbers.
"""

import re

COUNTRY_CODE = '234'

MOBILE_PREFIXES = {
	'0702': ['Starcomms', 'Visafone'],
	'0703': ['MTN Nigeria'],
	'0704': ['Visafone'],
	'0708': ['Zain'],
	'0802': ['Zain'],
	'0803': ['MTN Nigeria'],
	'0804': ['Mtel'],
	'0805': ['Glo Mobile'],
	'0806': ['MTN Nigeria'],
	'0807': ['Glo Mobile'],
	'0808': ['Zain'],
	'0809': ['Etisalat'],
}

AREA_CODES = {
	'01': ['Badagry', 'Ikorodu', 'Lagos'],
	'02': ['Ibadan'],
	'030': ['Ado-Ekiti', 'Ikere-Ekiti'],
	'031': ['Ilorin', 'Oro'],
	'034': ['Akure', 'Ondo'],
	'035': ['Ede', 'Osogbo'],
	'036': ['Ile-Ife'],
	'037': ['Ijebu-Ode', 'Sagamu'],
	'038': ['Iseyin', 'Ogbomoso', 'Oyo'],
	'039': ['Abeokuta', 'Ifo', 'Ilaro', 'Otta'],
	'042': ['Enugu', 'Nsukka'],
	'043': ['Abakiliki'],
	'044': ['Makurdi'],
	'045': ['Ikom', 'Obudu'],
	'046': ['Asaba', 'Akwa', 'Ihiala', 'Nnewi', 'Ogidi', 'Onitsha'],
	'047': ['Akwanga', 'Keffi', 'Nasarawa'],
	'051': ['Owo'],
	'052': ['Benin', 'Ekiadolor'],
	'053': ['Kwale', 'Orerokpe', 'Ozoro', 'Ughelli', 'Warri'],
	'054': ['Koko', 'Obiaruku', 'Oghare', 'Sapele'],
	'055': ['Agbor', 'Ekpoma', 'Isele-Uku', 'Ubiaja', 'Umunede', 'Uromi'],
	'057': ['Auchi', 'Igaira', 'Iguobazuwa', 'Sabongida-Ora'],
	'058': ['Ajaokuta', 'Lokoja'],
	'059': ['Okitipupa'],
	'060': ['Sokoto'],
	'062': ['Kaduna'],
	'064': ['Funtua', 'Kano'],
	'065': ['Daura', 'Katsina'],
	'066': ['Bida', 'Minna'],
	'067': ['Zuru'],
	'069': ['Zaria'],
	'070': ['Bauchi', 'Yelwa/Shendam'],
	'071': ['Azare'],
	'073': ['Bukuru', 'Jos', 'Lafia'],
	'075': ['Yola'],
	'076': ['Maiduguri'],
	'078': ['Gashua', 'Nguru'],
	'082': ['Abia'],
	'083': ['Orlu', 'Owerri'],
	'084': ['Abonema', 'Port Harcourt'],
	'085': ['Eket', 'Ikot-Ekpene', 'Uyo'],
	'086': ['Ahoda'],
	'087': ['Calabar'],
	'088': ['Umuahia'],
	'09': ['Abuja', 'Suleja'],
}

STRIP_SEPARATORS_RE = re.compile(r'[-+() ]+')
PHONE_NUMBER_RE = re.compile(
	r"""
	^										# Match the beginning of the number
	(										# Start of mobile number group
		(?P<mobile_prefix>%(nw_code)s)		# Match any one of the list of network codes
		(?P<mobile_number>[0-9]{7})			# Match a 7-digit number
	)										# End mobile number group
	|
	(										# Start of land line group
		(?P<area_code>%(area_code)s)		# Match any one of the list of area codes
		(?P<number>[0-9]{6,7})				# Match a 6- or 7-digit number
	)										# End land line group
	$										# Match the end of the number
	""" % {
		'nw_code': '|'.join(MOBILE_PREFIXES.keys()),
		'area_code': '|'.join(AREA_CODES.keys()),
	}, re.VERBOSE)

def strip_separators(value):
	"""
	Remove common phone number separators, including the leading "+", from the supplied ``value``.

		>>> strip_separators('(234) 803 508 2026')
		'2348035082026'
		>>> strip_separators('+234-803-508-2026')
		'2348035082026'
		>>> strip_separators('(0803) 508 2026')
		'08035082026'
		>>> strip_separators('08035082026')
		'08035082026'
	"""
	return STRIP_SEPARATORS_RE.sub('', value)

################
## Exceptions ##
################

try:
	BaseException
	# Python 2.5+ -- inherit from BaseException:
	class DialCodeError(BaseException): pass
	class PhoneNumberInvalid(BaseException): pass
except NameError:
	# Otherwise, just inherit from Exception:
	class DialCodeError(Exception): pass
	class PhoneNumberInvalid(Exception): pass

def clean_phone_number(value, include_prefix=False, grouped=True):
	"""
	Ensure the supplied ``value`` is a valid Nigerian phone number.

	For an invalid phone number, an exception will be raised::

		>>> clean_phone_number('12345678901')
		Traceback (most recent call last):
			...
		PhoneNumberInvalid: Invalid phone number.
		>>> clean_phone_number('+234 0803 508 2026')
		Traceback (most recent call last):
			...
		DialCodeError: Country code can't be followed by the digit '0'.

	If the supplied value is a valid phone number and ``grouped`` is ``True`` (the default),
	it is formatted and returned::

		>>> clean_phone_number('08035082026')
		'0803-508-2026'
		>>> clean_phone_number('(0803) 508 2026')
		'0803-508-2026'
		>>> clean_phone_number('(234) 803 508 2026')
		'0803-508-2026'

	If ``include_prefix`` is ``True``, include the country prefix in the returned number::

		>>> clean_phone_number('0803 508 2026', include_prefix=True)
		'(234) 803-508-2026'
		>>> clean_phone_number('(234) 803 508 2026', include_prefix=True)
		'(234) 803-508-2026'
		>>> clean_phone_number('+234 803 508 2026', include_prefix=True)
		'(234) 803-508-2026'

	If you don't care for the grouping, set ``grouped`` to ``False``::

		>>> clean_phone_number('0803 508 2026', include_prefix=True, grouped=False)
		'+2348035082026'
		>>> clean_phone_number('+234 803 508 2026', include_prefix=False, grouped=False)
		'08035082026'
	"""
	value = strip_separators(value)
	if value.startswith(COUNTRY_CODE):
		if value[3] == '0':
			raise DialCodeError("Country prefix can't be followed by the digit '0'.")
		else:
			value = '0' + value[3:]
	match = PHONE_NUMBER_RE.match(value)
	if not match:
		raise PhoneNumberInvalid('Invalid phone number.')
	captures = match.groupdict()
	separator = grouped and '-' or ''
	if captures['mobile_prefix']:
		value = separator.join([captures['mobile_prefix'], captures['mobile_number'][:3], captures['mobile_number'][3:]])
	else:
		value = separator.join([captures['area_code'], captures['number']])
	if include_prefix:
		if grouped:
			value = '(%s) %s' % (COUNTRY_CODE, value[1:])
		else:
			value = '+%s%s' % (COUNTRY_CODE, value[1:])

	return value

if __name__ == '__main__':
	import doctest
	doctest.testmod()
